//------------------------------------------------
// datapath.v
//
// James Forrest, 2013
// Based on code by:
// David_Harris@hmc.edu 3 November 2005
//
// Pipelined MIPS processor
//------------------------------------------------

module datapath(input         clk, reset,

                input  [31:0] instrF, readdataM,
                input         instrAckF, dataAckM,
                input         instrInvalidAddressF,

                input         memtoregE, memtoregM, memtoregW, cp0ToRegE, cp0ToRegW,
                input         pcsrcD, branchD,
                input         alusrcAE, alusrcBE,
                input  [1:0]  regdstE,
                input         regwriteE, regwriteM, regwriteW, 
                input         memwriteD, memwriteM, memwriteW, linkE,
                input  [1:0]  jumpD,
                input  [2:0]  compcontrolD,
                input  [3:0]  alucontrolE,
                input         signextD,

                output [31:0] pcF,
                output [31:0] aluoutM, writedata2M,

                output [5:0]  opD, functD,
                output [4:0]  rsD,
                output        compD,
                output        stallE, stallM, flushE, flushM, flushW,

                output  [7:0] cp0ReadAddressM, cp0WriteAddressW,
                output [31:0] pcD, pcE, pcM,
                output        flushD,
                output [31:0] cp0WriteDataW,
                output        overflowM, instrInvalidAddressM,
                input  [31:0] cp0ReadDataM,
                input         cp0JumpM,

                input   [4:0] debugAddress,
                inout  [31:0] debugData
				);

  wire        forwardaD, forwardbD;
  wire [1:0]  forwardaE, forwardbE;
  wire        stallF;
  wire [4:0]  rtD, rdD, rsE, rtE, rdE;
  wire [4:0]  writeregE, writeregM, writeregW;
  wire [31:0] pcFD, pcnextFD, pcnextbrFD, pcplus4F, pcplus4D, pcbranchD, pcplus4E;
  wire [31:0] immD, immE, immshD;
  wire [31:0] srcaD, srca2D, srcaE, srca2E, srca3E;
  wire [31:0] srcbD, srcb2D, srcbE, srcb2E, srcb3E;
  wire [31:0] instrD;
  wire [31:0] aluoutE, aluout2E, aluoutW;
  wire [31:0] readdataW, writedataM, resultW, result2W, cp0ReadDataW;

  // hazard detection
  hazard    h(rsD, rtD, rsE, rtE, writeregE, writeregM, writeregW, 
              regwriteE, regwriteM, regwriteW, cp0ToRegE,
              memtoregE, memtoregM, memwriteD, memwriteM, memwriteW,
              pcsrcD, branchD, cp0JumpM, jumpD,
              forwardaD, forwardbD, forwardaE, forwardbE, forwardM,
              stallF, stallD, stallE, stallM, flushD, flushE, flushM, flushW,
              instrAckF, dataAckM);

  // next PC logic (operates in fetch and decode (and slightly M now too))
  mux2 #(32)  pcbrmux(pcplus4F, pcbranchD, pcsrcD, pcnextbrFD);
  mux3 #(32)  pcjumux(pcnextbrFD, {pcplus4D[31:28], instrD[25:0], 2'b00}, srca2D,
                      jumpD, pcnextFD);
  mux2 #(32)  pccp0mux(pcnextFD, cp0ReadDataM, cp0JumpM, pcFD);

  // register file (operates in decode and writeback)
  regfile     rf(clk, regwriteW, rsD, rtD, writeregW,
                 result2W, srcaD, srcbD, debugAddress, debugData);

  // Fetch stage logic
  flopenr #(32) pcreg(clk, reset, ~stallF, pcFD, pcF);
  adder         pcadd1(pcF, 32'b100, pcplus4F);

  // Decode stage 
  flopenrc #(97) regD(clk, reset, ~stallD, flushD,
                      {pcplus4F, pcF, instrF, instrInvalidAddressF},
                      {pcplus4D, pcD, instrD, instrInvalidAddressD});
  ext         ext(instrD[15:0], signextD, immD);
  sl2         immsh(immD, immshD);
  adder       pcadd2(pcplus4D, immshD, pcbranchD);
  mux2 #(32)  forwardadmux(srcaD, aluoutM, forwardaD, srca2D);
  mux2 #(32)  forwardbdmux(srcbD, aluoutM, forwardbD, srcb2D);
  comp        comp(srca2D, srcb2D, rtD, compcontrolD, compD);

  assign opD = instrD[31:26];
  assign functD = instrD[5:0];
  assign rsD = instrD[25:21];
  assign rtD = instrD[20:16];
  assign rdD = instrD[15:11];

  // Execute stage 
  flopenrc #(176) regE(clk, reset, ~stallE, flushE,
                    {srcaD, srcbD, immD, rsD, rtD, rdD, pcplus4D, pcD, instrInvalidAddressD},
                    {srcaE, srcbE, immE, rsE, rtE, rdE, pcplus4E, pcE, instrInvalidAddressE});

  mux3 #(32)  forwardaemux(srcaE, result2W, aluoutM, forwardaE, srca2E);
  mux3 #(32)  forwardbemux(srcbE, result2W, aluoutM, forwardbE, srcb2E);
  wire [31:0] writeDataE = srcb2E;
  mux2 #(32)  srcamux(srca2E, immE, alusrcAE, srca3E);
  mux2 #(32)  srcbmux(srcb2E, immE, alusrcBE, srcb3E);
  alu         alu(srca3E, srcb3E, alucontrolE, aluoutE, overflowE);
  mux2 #(32)  linkmux(aluoutE, pcplus4E, linkE, aluout2E);
  mux4 #(5)   wrmux(rtE, rdE, 5'd31, 5'd0, regdstE, writeregE);

  wire [7:0] cp0RegisterAddressE = {immE[2:0], rdE};

  // Memory stage
  flopenrc #(111) regM(clk, reset, ~stallM, flushM,
                 {writeDataE, aluout2E, writeregE, cp0RegisterAddressE, pcE, overflowE, instrInvalidAddressE}, 
                 {writedataM, aluoutM , writeregM, cp0ReadAddressM    , pcM, overflowM, instrInvalidAddressM});
  mux2  #(32) forwardmmux(writedataM, result2W, forwardM, writedata2M);

  // Writeback stage
  floprc #(141) regW(clk, reset, flushW,
                 {aluoutM, readdataM, writeregM, cp0ReadDataM, cp0ReadAddressM , writedata2M},
                 {aluoutW, readdataW, writeregW, cp0ReadDataW, cp0WriteAddressW, cp0WriteDataW});
  mux2 #(32)  resmux1(aluoutW, readdataW, memtoregW, resultW);
  mux2 #(32)  resmux2(resultW, cp0ReadDataW, cp0ToRegW, result2W);

endmodule
